package game.games.gol;

import game.GameListener;
import game.GraphicUtil;
import game.Logger;
import game.Position;
import game.Util;
import game.cellmap.AbstractCellMapGame;
import game.cellmap.CellMap;
import game.cellsize.ICellSizeChangeListener;
import game.console.GameConsole;
import game.console.IConsoleCommand;
import game.coord.MapGenerator;
import game.scrollable.INeverScrollable;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javax.swing.SwingUtilities;

public class GameOfLife extends AbstractCellMapGame<CellMap> implements INeverScrollable, ICellSizeChangeListener
{
    private static GameOfLife instance;
    
    public synchronized static GameOfLife getInstance()
    {
        if (instance == null)
        {
            synchronized (GameOfLife.class)
            {
                instance = new GameOfLife();
                for (IConsoleCommand commandObj : GameOfLifeCommands.getCommands())
                {
                    GameConsole.getInstance().addConsoleCommand(commandObj);
                }
            }
        }
        return instance;
    }
    
    private List<Cell>     cells;
    
    private int            generation           = 0;
    private int            livingCells          = 0;
    private int            livingCellsIdentical = 0;
    private int            nextLivingCells      = 15000;
    
    private GameOfLifeRule rule                 = GameOfLifeRule.DEFAULT;
    
    private ColorSet       colorSet             = ColorSet.DEFAULT;
    private static boolean CREATE_LIVE_MODE     = false;
    
    public GameOfLife()
    {
        super(false, new CellMap(true));
        final GameListener gameListener = GameListener.getInstance();
        gameListener.addKeyListener(new KeyAdapter() {
            private boolean IGNORE_KEYBOARD = false;
            
            @Override
            public void keyPressed(KeyEvent e)
            {
                if (IGNORE_KEYBOARD)
                {
                    e.consume();
                }
                else
                {
                    IGNORE_KEYBOARD = true;
                    final GameConsole console = GameConsole.getInstance();
                    if (console.isOpen() == false)
                    {
                        switch (e.getKeyCode())
                        {
                            case KeyEvent.VK_P:
                            {
                                if (instance.isRunning())
                                {
                                    if (instance.isPaused())
                                    {
                                        instance.resume();
                                    }
                                    else
                                    {
                                        instance.pause();
                                    }
                                }
                                break;
                            }
                            case KeyEvent.VK_S:
                            {
                                instance.requestStop();
                                break;
                            }
                            case KeyEvent.VK_R:
                            {
                                instance.requestRestart();
                                break;
                            }
                            default:
                            {
                                getMap().getKeyAdapter().keyPressed(e);
                                break;
                            }
                        }
                    }
                    IGNORE_KEYBOARD = false;
                }
            }
        });
        gameListener.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                if (SwingUtilities.isRightMouseButton(e))
                {
                    CREATE_LIVE_MODE = CREATE_LIVE_MODE == false;
                    Logger.log("CREATIVE_LIVE_MODE " + CREATE_LIVE_MODE);
                }
            }
        });
        gameListener.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                if (CREATE_LIVE_MODE)
                {
                    try
                    {
                        Position pos = getMap().calcCoord(e.getPoint());
                        Logger.log("SURVIVING: " + pos);
                        Cell cell = getCell(pos);
                        cell.setAlive(true);
                        cell.setSurviving(true);
                    }
                    catch (IllegalArgumentException ex)
                    {
                        ex.printStackTrace();
                    }
                }
            }
        });
    }
    
    public void setRule(String rule)
    {
        GameOfLifeRule newRule = new GameOfLifeRule(rule);
        setRule(newRule);
    }
    
    public void setRule(GameOfLifeRule newRule)
    {
        try
        {
            pause();
            rule = newRule;
            resume();
        }
        catch (Exception ex)
        {
            Logger.log(ex);
        }
    }
    
    private Cell getCell(Position position)
    {
        int index = getMap().calculateIndex(position);
        return cells.get(index);
    }
    
    public void setNextLivingCells(int livingCells)
    {
        if (livingCells > cells.size())
        {
            livingCells = cells.size();
        }
        this.nextLivingCells = livingCells;
    }
    
    public void setProzentLivingCells(int prozent)
    {
        if (prozent >= 100)
        {
            setNextLivingCells(cells.size());
        }
        else
        {
            setNextLivingCells((cells.size() / 100) * prozent);
        }
    }
    
    private final int maxThreads = 8;
    
    private class RandomLivingCellThread extends Thread
    {
        private List<Integer> livingCells = new ArrayList<Integer>();
        
        public RandomLivingCellThread(String string)
        {
            super(string);
        }
        
        public List<Integer> getLivingCells()
        {
            return livingCells;
        }
        
        @Override
        public void run()
        {
            // Lebende Zellen generieren
            Random RANDOM = new Random(System.nanoTime());
            do
            {
                int index = RANDOM.nextInt(getMap().getMapSize().getSum());
                if (livingCells.contains(index) == false)
                {
                    livingCells.add(index);
                }
            }
            while (livingCells.size() < 250);
        }
    }
    
    private static Thread AddLivingCellsThreadInstance = null;
    private int           cellsFound                   = 0;
    
    private class AddLivingCellsThread implements Runnable
    {
        @Override
        public void run()
        {
            long startTime = System.nanoTime();
            final List<Integer> livingCells = Collections.synchronizedList(new ArrayList<Integer>());
            do
            {
                List<RandomLivingCellThread> threads = new ArrayList<RandomLivingCellThread>();
                while (isResetting() && threads.size() < maxThreads)
                {
                    RandomLivingCellThread t = new RandomLivingCellThread("RandomLivingCellsThread " + threads.size());
                    t.start();
                    threads.add(t);
                }
                for (RandomLivingCellThread thread : threads)
                {
                    while (isResetting() && thread.isAlive())
                    {
                        // return
                        try
                        {
                            thread.join();
                        }
                        catch (InterruptedException e)
                        {
                            //
                        }
                    }
                    List<Integer> cellPoints = thread.getLivingCells();
                    for (Integer p : cellPoints)
                    {
                        if (livingCells.contains(p) == false)
                        {
                            livingCells.add(p);
                        }
                    }
                }
                cellsFound = livingCells.size();
                Logger.log(Thread.currentThread().getName() + ": " + cellsFound + " cells of " + nextLivingCells + " found");
            }
            while (isResetting() && livingCells.size() < nextLivingCells);
            while (isResetting() && livingCells.size() > nextLivingCells)
            {
                // return
                livingCells.remove(0);
            }
            Logger.log(livingCells.size() + " living cells found (" + Util.nanoToSec(System.nanoTime() - startTime) + " sec)");
            addLivingCells(livingCells);
        }
    }
    
    private void addLivingCells()
    {
        if (AddLivingCellsThreadInstance == null)
        {
            AddLivingCellsThreadInstance = new Thread(new AddLivingCellsThread(), "AddLivingCellsThread");
            AddLivingCellsThreadInstance.start();
        }
        else
        {
            while (AddLivingCellsThreadInstance.isAlive())
            {
                try
                {
                    AddLivingCellsThreadInstance.join();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            AddLivingCellsThreadInstance = null;
            addLivingCells();
        }
    }
    
    private void addLivingCells(List<Integer> livingCells)
    {
        // reset count
        for (Integer index : livingCells)
        {
            // return
            if (isResetting())
            {
                Cell cell = getCell(getMap().calculatePosition(index));
                cell.setAlive(true);
                cell.setSurviving(true);
            }
        }
        super.reset();
    }
    
    @Override
    protected void reset()
    {
        long startTime = System.nanoTime();
        recalcSize(getFrameSize());
        for (Iterator<Cell> i = cells.iterator(); i.hasNext();)
        {
            Cell cell = i.next();
            cell.setAlive(false);
            cell.setSurviving(false);
        }
        Logger.log("resetGeneration(" + cells.size() + ") (" + +Util.nanoToSec(System.nanoTime() - startTime) + " sec)");
        addLivingCells();
        generation = 0;
    }
    
    @Override
    protected void start()
    {
        Logger.log("start()");
        long startTime = System.nanoTime();
        // init
        getMap().addCellSizeChangeListener(this);
        getMap().setCellSizes(1, 3, 5, 7, 10, 12, 15, 18, 20, 25);
        cells = new ArrayList<Cell>();
        // Anzahl Zellen
        Dimension size = getFrameSize();
        Position maxSize = getMap().calcMaxSize(new Position(size));
        getMap().setMapSize(maxSize);
        // Liste generieren
        cells.clear();
        for (Cell cell : MapGenerator.generateMooreMap(maxSize, Cell.class))
        {
            cells.add(cell);
        }
        if (nextLivingCells > cells.size())
        {
            setProzentLivingCells(25);
        }
        Logger.log("initGeneration(" + maxSize.getX() + "/" + maxSize.getY() + ") complete, " + cells.size() + " cells created (" + +Util.nanoToSec(System.nanoTime() - startTime) + " sec)");
    }
    
    public void setColorSet(ColorSet colorSet)
    {
        this.colorSet = colorSet;
    }
    
    public ColorSet getColorSet()
    {
        return colorSet;
    }
    
    @Override
    public void render(Graphics g)
    {
        GraphicUtil.clearScreen((Graphics2D) g, getFrameSize().width, getFrameSize().height);
        if (isRunning())
        {
            for (int i = 0; i < cells.size(); i++)
            {
                Cell cell = cells.get(i);
                Position position = getMap().calculatePosition(i);
                Color color = colorSet.getColor(cell.isAlive(), cell.isSurviving());
                getMap().fillRect(g, position.getWidth(), position.getHeight(), color);
                if (isPaused() == false)
                {
                    // next round
                    cell.setAlive(cell.isSurviving());
                }
            }
        }
        else if (isResetting())
        {
            GraphicUtil.renderCenterText(g, cellsFound + " of " + nextLivingCells + " found!", 12, new Position(getFrameSize()));
        }
        else
        {
            GraphicUtil.renderCenterText(g, "Status: " + getStatus(), 30, new Position(getFrameSize()));
        }
    }
    
    @Override
    public void doNext()
    {
        generation++;
        int alive = 0;
        for (int i = 0; i < cells.size(); i++)
        {
            Cell cell = cells.get(i);
            int livingNeighbors = cell.countLivingNeighbors();
            cell.setSurviving(rule.isSurviving(cell.isAlive(), livingNeighbors));
            if (cell.isAlive())
            {
                alive++;
            }
        }
        if (alive == livingCells)
        {
            livingCellsIdentical++;
        }
        else
        {
            livingCellsIdentical = 0;
        }
        livingCells = alive;
        setInfo("Generation " + generation + "; " + livingCells + " Living Cells");
        if (livingCells <= 0)
        {
            Logger.log("No more living cells");
            requestRestart();
        }
        else if (livingCellsIdentical > 50)
        {
            requestRestart();
        }
    }
    
    public static void main(String[] args)
    {
        Thread t = new Thread(GameOfLife.getInstance(), "GameOfLife");
        t.start();
    }
    
    @Override
    public void recalcSize(Dimension windowSize)
    {
        Position maxXY = getMap().calcMaxSize(new Position(windowSize));
        if (maxXY.equals(getMap().getMapSize()) == false)
        {
            getMap().setMapSize(maxXY);
            int sum = maxXY.getSum();
            if (this.cells.size() != sum)
            {
                List<Cell> cells = new ArrayList<Cell>();
                for (Cell cell : MapGenerator.generateMooreMap(maxXY, Cell.class))
                {
                    cells.add(cell);
                }
                this.cells = cells;
                this.setProzentLivingCells(30);
            }
        }
    }
    
    @Override
    public void cellSizeChanged(int newCellSize)
    {
        Dimension size = getFrameSize();
        recalcSize(size);
    }
}
